package models

import (
	"fmt"

	"gitee.com/hakwolf/dbmagic/golib/internal/modules/app"
	"gitee.com/hakwolf/dbmagic/golib/internal/modules/logger"
	"gitee.com/hakwolf/dbmagic/golib/internal/modules/setting"
	"gitee.com/hakwolf/dbmagic/golib/internal/routers/global"
	"gitee.com/hakwolf/dbmagic/golib/internal/sqldriver"
	"github.com/go-sql-driver/mysql"
	"github.com/go-xorm/core"
	"github.com/go-xorm/xorm"
	_ "github.com/lib/pq"
	_ "github.com/mattn/go-adodb"
	_ "github.com/mattn/go-sqlite3"
	_ "github.com/microsoft/go-mssqldb"
	"gopkg.in/macaron.v1"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

type Status int8
type CommonMap map[string]interface{}

var TablePrefix = ""
var Db *xorm.Engine

const (
	Disabled Status = 0 // 禁用
	Failure  Status = 0 // 失败
	Enabled  Status = 1 // 启用
	Running  Status = 1 // 运行中
	Finish   Status = 2 // 完成
	Cancel   Status = 3 // 取消
)

const (
	Page        = 1      // 当前页数
	PageSize    = 20     // 每页多少条数据
	MaxPageSize = 100000 // 每次最多取多少条
)

const DefaultTimeFormat = "2006-01-02 15:04:05"

type BaseModel struct {
	Page     int `xorm:"-"`
	PageSize int `xorm:"-"`
}

func (model *BaseModel) parsePageAndPageSize(params CommonMap) {
	page, ok := params["Page"]
	if ok {
		model.Page = page.(int)
	}
	pageSize, ok := params["PageSize"]
	if ok {
		model.PageSize = pageSize.(int)
	}
	if model.Page <= 0 {
		model.Page = Page
	}
	if model.PageSize <= 0 {
		model.PageSize = MaxPageSize
	}
}

func (model *BaseModel) pageLimitOffset() int {
	return (model.Page - 1) * model.PageSize
}

// 创建Db
func CreateDb() *xorm.Engine {
	dsn := getDbEngineDSN(app.Setting)
	engine, err := xorm.NewEngine(app.Setting.Db.Engine, dsn)
	if err != nil {
		logger.Fatal("创建xorm引擎失败", err)
	}
	engine.SetMaxIdleConns(app.Setting.Db.MaxIdleConns)
	engine.SetMaxOpenConns(app.Setting.Db.MaxOpenConns)

	if app.Setting.Db.Prefix != "" {
		// 设置表前缀
		TablePrefix = app.Setting.Db.Prefix
		mapper := core.NewPrefixMapper(core.SnakeMapper{}, app.Setting.Db.Prefix)
		engine.SetTableMapper(mapper)
	}
	// 本地环境开启日志
	if macaron.Env == macaron.DEV {
		engine.ShowSQL(true)
		engine.Logger().SetLevel(core.LOG_DEBUG)
	}

	go keepDbAlived(engine)

	return engine
}

// 创建Db
func CreateSqliteDb() *xorm.Engine {
	dsn := getSqliteDbEngineDSN()
	engine, err := xorm.NewEngine("sqlite3", dsn)
	if err != nil {
		logger.Fatal("创建xorm引擎失败", err)
	}
	engine.SetMaxIdleConns(30)  //app.Setting.Db.MaxIdleConns
	engine.SetMaxOpenConns(100) //app.Setting.Db.MaxOpenConns

	//if app.Setting.Db.Prefix != "" {
	//	// 设置表前缀
	//	TablePrefix = app.Setting.Db.Prefix
	//	mapper := core.NewPrefixMapper(core.SnakeMapper{}, app.Setting.Db.Prefix)
	//	engine.SetTableMapper(mapper)
	//}
	// 本地环境开启日志
	//if macaron.Env == macaron.DEV {//打印sql
	//	engine.ShowSQL(true)
	//	engine.Logger().SetLevel(core.LOG_DEBUG)
	//}

	//go keepDbAlived(engine)

	return engine
}

// 创建Db
func CreateCliSqliteDb() *xorm.Engine {
	dsn := getSqliteCliDbEngineDSN()
	engine, err := xorm.NewEngine("sqlite3", dsn)
	if err != nil {
		logger.Fatal("创建xorm引擎失败", err)
	}
	engine.SetMaxIdleConns(30)  //app.Setting.Db.MaxIdleConns
	engine.SetMaxOpenConns(100) //app.Setting.Db.MaxOpenConns

	//if app.Setting.Db.Prefix != "" {
	//	// 设置表前缀
	//	TablePrefix = app.Setting.Db.Prefix
	//	mapper := core.NewPrefixMapper(core.SnakeMapper{}, app.Setting.Db.Prefix)
	//	engine.SetTableMapper(mapper)
	//}
	// 本地环境开启日志
	//if macaron.Env == macaron.DEV {  //打印sql
	//	engine.ShowSQL(true)
	//	engine.Logger().SetLevel(core.LOG_DEBUG)
	//}

	//go keepDbAlived(engine)

	return engine
}

// 创建临时数据库连接
func CreateTmpDb(setting *setting.Setting) (*xorm.Engine, error) {
	dsn := getDbEngineDSN(setting)

	//ParseDSN()   mysql包中的dsn.go里的 引用，跨包的话前面加引用的包名

	return xorm.NewEngine(setting.Db.Engine, dsn)
}

// 获取数据库引擎DSN  mysql,sqlite,postgres
func getDbEngineDSN(setting *setting.Setting) string {
	engine := strings.ToLower(setting.Db.Engine)
	dsn := ""
	switch engine {
	case "mysql": //?allowNativePasswords=true mysql8.0 报this user requires mysql native password authentication 不知道连5.7有没有问题。
		dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s",
			setting.Db.User,
			setting.Db.Password,
			setting.Db.Host,
			setting.Db.Port,
			setting.Db.Database,
			setting.Db.Charset)

		cfg, err := mysql.ParseDSN(dsn) //8.0  //?allowNativePasswords=true mysql8.0 报this user requires mysql native password authentication 不知道连5.7有没有问题。
		if err == nil {
			cfg.AllowNativePasswords = true //modfy by qjh test
		}
		dsn = (*mysql.Config).FormatDSN(cfg) // mysql.Config.FormatDSN( cfg) 这样写报这个错 invalid method expression mysql.Config.FormatDSN (needs pointer receiver: (*mysql.Config).FormatDSN)

	case "postgres":
		dsn = fmt.Sprintf("user=%s password=%s host=%s port=%d dbname=%s sslmode=disable",
			setting.Db.User,
			setting.Db.Password,
			setting.Db.Host,
			setting.Db.Port,
			setting.Db.Database)

	case "mssql":
		//dsn = fmt.Sprintf("user=%s password=%s host=%s port=%d dbname=%s sslmode=disable",
		dsn = fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d;encrypt=disable",
			setting.Db.Host,
			setting.Db.Database,
			setting.Db.User,
			setting.Db.Password,
			setting.Db.Port,
		)
	case "adodb":
		//dsn = fmt.Sprintf("user=%s password=%s host=%s port=%d dbname=%s sslmode=disable",
		var dataSource string
		if setting.Db.Port != 0 {
			dataSource = setting.Db.Host + "," + strconv.Itoa(setting.Db.Port)
		} else {
			dataSource = setting.Db.Host
		}
		db := sqldriver.Mssql{
			DataSource: dataSource,
			Database:   setting.Db.Database,
			// windwos: true 为windows身份验证，false 必须设置sa账号和密码
			Windows: false,
			Sa: sqldriver.SA{
				User:   setting.Db.User,
				Passwd: setting.Db.Password,
			},
		}
		dsn = db.GetDsn()

	case "sqlite3":
		//dsn = fmt.Sprintf("user=%s password=%s host=%s port=%d dbname=%s sslmode=disable",
		/*		dsn = fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d;encrypt=disable",
				setting.Db.Host,
				setting.Db.Database,
				setting.Db.User,
				setting.Db.Password,
				setting.Db.Port,
			)*/
		/*		os.Remove("./foo.db")

				db, err := sql.Open("sqlite3", "./foo.db")
				if err != nil {
					log.Fatal(err)
				}*/
		db := "./" + setting.Db.Database
		dsn = db
	}

	return dsn
}

func getSqliteDbEngineDSN() string {
	db := filepath.Join(app.AppDir, global.AppserverDb)
	return db
}

func getSqliteCliDbEngineDSN() string {
	db := filepath.Join(app.AppDir, global.AppClientDb)
	return db
}

func keepDbAlived(engine *xorm.Engine) {
	t := time.Tick(180 * time.Second)
	for {
		<-t
		engine.Ping()
	}
}
